1use crate::ext::io::*;
3use crate::ext::psb::*;
4use crate::scripts::base::*;
5use crate::try_option;
6use crate::types::*;
7use crate::utils::img::*;
8use anyhow::Result;
9use emote_psb::PsbReader;
10use libtlg_rs::*;
11use std::collections::HashMap;
12use std::io::{Read, Seek};
13use std::path::Path;
14
15#[derive(Debug)]
16pub struct PImgBuilder {}
18
19impl PImgBuilder {
20 pub const fn new() -> Self {
22 Self {}
23 }
24}
25
26impl ScriptBuilder for PImgBuilder {
27 fn default_encoding(&self) -> Encoding {
28 Encoding::Utf8
29 }
30
31 fn build_script(
32 &self,
33 buf: Vec<u8>,
34 filename: &str,
35 _encoding: Encoding,
36 _archive_encoding: Encoding,
37 config: &ExtraConfig,
38 _archive: Option<&Box<dyn Script>>,
39 ) -> Result<Box<dyn Script>> {
40 Ok(Box::new(PImg::new(MemReader::new(buf), filename, config)?))
41 }
42
43 fn build_script_from_file(
44 &self,
45 filename: &str,
46 _encoding: Encoding,
47 _archive_encoding: Encoding,
48 config: &ExtraConfig,
49 _archive: Option<&Box<dyn Script>>,
50 ) -> Result<Box<dyn Script>> {
51 if filename == "-" {
52 let data = crate::utils::files::read_file(filename)?;
53 Ok(Box::new(PImg::new(MemReader::new(data), filename, config)?))
54 } else {
55 let f = std::fs::File::open(filename)?;
56 let reader = std::io::BufReader::new(f);
57 Ok(Box::new(PImg::new(reader, filename, config)?))
58 }
59 }
60
61 fn build_script_from_reader(
62 &self,
63 reader: Box<dyn ReadSeek>,
64 filename: &str,
65 _encoding: Encoding,
66 _archive_encoding: Encoding,
67 config: &ExtraConfig,
68 _archive: Option<&Box<dyn Script>>,
69 ) -> Result<Box<dyn Script>> {
70 Ok(Box::new(PImg::new(reader, filename, config)?))
71 }
72
73 fn extensions(&self) -> &'static [&'static str] {
74 &["pimg"]
75 }
76
77 fn script_type(&self) -> &'static ScriptType {
78 &ScriptType::EmotePimg
79 }
80
81 fn is_this_format(&self, filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
82 if Path::new(filename)
83 .extension()
84 .map(|ext| ext.to_ascii_lowercase() == "pimg")
85 .unwrap_or(false)
86 && buf_len >= 4
87 && buf.starts_with(b"PSB\0")
88 {
89 return Some(255);
90 }
91 None
92 }
93
94 fn is_image(&self) -> bool {
95 true
96 }
97}
98
99#[derive(Debug)]
100pub struct PImg {
102 psb: VirtualPsbFixed,
103 overlay: Option<bool>,
104}
105
106impl PImg {
107 pub fn new<R: Read + Seek>(reader: R, filename: &str, config: &ExtraConfig) -> Result<Self> {
113 let mut psb = PsbReader::open_psb(reader)
114 .map_err(|e| anyhow::anyhow!("Failed to open PSB from {}: {:?}", filename, e))?;
115 let psb = psb
116 .load()
117 .map_err(|e| anyhow::anyhow!("Failed to load PSB from {}: {:?}", filename, e))?
118 .to_psb_fixed();
119 Ok(Self {
120 psb,
121 overlay: config.emote_pimg_overlay,
122 })
123 }
124
125 fn load_img(&self, layer_id: i64) -> Result<Tlg> {
126 let layer_id = layer_id as usize;
127 let psb = self.psb.root();
128 let reference = &psb[format!("{layer_id}.tlg")];
129 let resource_id = reference
130 .resource_id()
131 .ok_or_else(|| anyhow::anyhow!("Layer {layer_id} does not have a resource ID"))?
132 as usize;
133 if resource_id >= self.psb.resources().len() {
134 return Err(anyhow::anyhow!(
135 "Resource ID {resource_id} for layer {layer_id} is out of bounds"
136 ));
137 }
138 let resource = &self.psb.resources()[resource_id];
139 Ok(load_tlg(MemReaderRef::new(&resource))?)
140 }
141}
142
143impl Script for PImg {
144 fn default_output_script_type(&self) -> OutputScriptType {
145 OutputScriptType::Json
146 }
147
148 fn default_format_type(&self) -> FormatOptions {
149 FormatOptions::None
150 }
151
152 fn is_image(&self) -> bool {
153 true
154 }
155
156 fn is_multi_image(&self) -> bool {
157 true
158 }
159
160 fn export_multi_image<'a>(
161 &'a self,
162 ) -> Result<Box<dyn Iterator<Item = Result<ImageDataWithName>> + 'a>> {
163 let psb = self.psb.root();
164 let overlay = self.overlay.unwrap_or_else(|| {
165 psb["layers"]
166 .members()
167 .all(|layer| layer["group_layer_id"].is_none())
168 });
169 if !overlay {
170 return Ok(Box::new(PImgIter2 {
171 pimg: self,
172 layers: psb.iter(),
173 }));
174 }
175 let width = psb["width"]
176 .as_u32()
177 .ok_or(anyhow::anyhow!("missing width"))?;
178 let height = psb["height"]
179 .as_u32()
180 .ok_or(anyhow::anyhow!("missing height"))?;
181 if !psb["layers"].is_list() {
182 return Err(anyhow::anyhow!("layers is not a list"));
183 }
184 if psb["layers"].len() == 0 {
185 return Ok(Box::new(std::iter::empty()));
186 }
187 let mut bases = HashMap::new();
188 for i in psb["layers"].members() {
189 if !i["diff_id"].is_none() {
190 continue; }
192 let layer_id = i["layer_id"]
193 .as_i64()
194 .ok_or(anyhow::anyhow!("missing layer_id"))?;
195 let top = i["top"].as_u32().ok_or(anyhow::anyhow!("missing top"))?;
196 let left = i["left"].as_u32().ok_or(anyhow::anyhow!("missing left"))?;
197 let opacity = i["opacity"]
198 .as_u8()
199 .ok_or_else(|| anyhow::anyhow!("Layer does not have a valid opacity"))?;
200 bases.insert(layer_id, (self.load_img(layer_id)?, top, left, opacity));
201 }
202 Ok(Box::new(PImgIter {
203 pimg: self,
204 width,
205 height,
206 layers: psb["layers"].members(),
207 bases,
208 }))
209 }
210}
211
212struct PImgIter<'a> {
213 pimg: &'a PImg,
214 width: u32,
215 height: u32,
216 layers: ListIter<'a>,
217 bases: HashMap<i64, (Tlg, u32, u32, u8)>,
218}
219
220impl<'a> Iterator for PImgIter<'a> {
221 type Item = Result<ImageDataWithName>;
222
223 fn next(&mut self) -> Option<Self::Item> {
224 match self.layers.next() {
225 Some(layer) => {
226 let layer_id =
227 try_option!(layer["layer_id"].as_i64().ok_or_else(|| {
228 anyhow::anyhow!("Layer does not have a valid layer_id")
229 }));
230 let layer_name = try_option!(
231 layer["name"]
232 .as_str()
233 .ok_or_else(|| { anyhow::anyhow!("Layer does not have a valid name") })
234 );
235 let width = try_option!(
236 layer["width"]
237 .as_u32()
238 .ok_or_else(|| { anyhow::anyhow!("Layer does not have a valid width") })
239 );
240 let height = try_option!(
241 layer["height"]
242 .as_u32()
243 .ok_or_else(|| { anyhow::anyhow!("Layer does not have a valid height") })
244 );
245 let top = try_option!(
246 layer["top"]
247 .as_u32()
248 .ok_or_else(|| { anyhow::anyhow!("Layer does not have a valid top") })
249 );
250 let left = try_option!(
251 layer["left"]
252 .as_u32()
253 .ok_or_else(|| { anyhow::anyhow!("Layer does not have a valid left") })
254 );
255 let opacity = try_option!(
256 layer["opacity"]
257 .as_u8()
258 .ok_or_else(|| { anyhow::anyhow!("Layer does not have a valid opacity") })
259 );
260 if layer["diff_id"].is_none() {
261 let base = &try_option!(self.bases.get(&layer_id).ok_or(anyhow::anyhow!(
262 "Base image for layer_id {} not found",
263 layer_id
264 )))
265 .0;
266 let mut data = ImageData {
267 width: self.width,
268 height: self.height,
269 color_type: match base.color {
270 TlgColorType::Bgr24 => ImageColorType::Bgr,
271 TlgColorType::Bgra32 => ImageColorType::Bgra,
272 TlgColorType::Grayscale8 => ImageColorType::Grayscale,
273 },
274 depth: 8,
275 data: base.data.clone(),
276 };
277 if opacity != 255 {
278 try_option!(apply_opacity(&mut data, opacity));
279 }
280 if self.width != width || self.height != height || top != 0 || left != 0 {
281 data =
282 try_option!(draw_on_canvas(data, self.width, self.height, left, top));
283 }
284 return Some(Ok(ImageDataWithName {
285 name: layer_name.to_string(),
286 data,
287 }));
288 } else {
289 let diff_id =
290 try_option!(layer["diff_id"].as_i64().ok_or_else(|| {
291 anyhow::anyhow!("Layer does not have a valid diff_id")
292 }));
293 let (base, base_top, base_left, base_opacity) = try_option!(
294 self.bases
295 .get(&diff_id)
296 .ok_or(anyhow::anyhow!("Base image layer {} not found", diff_id))
297 );
298 let diff = try_option!(self.pimg.load_img(layer_id));
299 if base.color != diff.color {
300 return Some(Err(anyhow::anyhow!(
301 "Color type mismatch for layer_id {}: base color {:?}, diff color {:?}",
302 layer_id,
303 base.color,
304 diff.color
305 )));
306 }
307 let mut base_img = ImageData {
308 width: base.width,
309 height: base.height,
310 color_type: match base.color {
311 TlgColorType::Bgr24 => ImageColorType::Bgr,
312 TlgColorType::Bgra32 => ImageColorType::Bgra,
313 TlgColorType::Grayscale8 => ImageColorType::Grayscale,
314 },
315 depth: 8,
316 data: base.data.clone(),
317 };
318 if base.width != self.width
319 || base.height != self.height
320 || *base_top != 0
321 || *base_left != 0
322 {
323 base_img = try_option!(draw_on_canvas(
324 base_img,
325 self.width,
326 self.height,
327 *base_left,
328 *base_top
329 ));
330 }
331 if *base_opacity != 255 {
332 try_option!(apply_opacity(&mut base_img, *base_opacity));
333 }
334 let diff = ImageData {
335 width: diff.width,
336 height: diff.height,
337 color_type: match diff.color {
338 TlgColorType::Bgr24 => ImageColorType::Bgr,
339 TlgColorType::Bgra32 => ImageColorType::Bgra,
340 TlgColorType::Grayscale8 => ImageColorType::Grayscale,
341 },
342 depth: 8,
343 data: diff.data.clone(),
344 };
345 try_option!(draw_on_img_with_opacity(
346 &mut base_img,
347 &diff,
348 left,
349 top,
350 opacity
351 ));
352 Some(Ok(ImageDataWithName {
353 name: layer_name.to_string(),
354 data: base_img,
355 }))
356 }
357 }
358 None => None,
359 }
360 }
361}
362
363struct PImgIter2<'a> {
364 pimg: &'a PImg,
365 layers: ObjectIter<'a>,
366}
367
368impl<'a> Iterator for PImgIter2<'a> {
369 type Item = Result<ImageDataWithName>;
370
371 fn next(&mut self) -> Option<Self::Item> {
372 match self.layers.next() {
373 Some((k, v)) => {
374 if !k.ends_with(".tlg") {
375 return self.next();
376 }
377 let resource_id = try_option!(
378 v.resource_id()
379 .ok_or_else(|| anyhow::anyhow!("Layer {} does not have a resource ID", k))
380 ) as usize;
381 let name = k.trim_end_matches(".tlg").to_string();
382 if resource_id >= self.pimg.psb.resources().len() {
383 return Some(Err(anyhow::anyhow!(
384 "Resource ID {} for layer {} is out of bounds",
385 resource_id,
386 k
387 )));
388 }
389 let resource = &self.pimg.psb.resources()[resource_id];
390 let tlg = try_option!(load_tlg(MemReaderRef::new(&resource)));
391 Some(Ok(ImageDataWithName {
392 name,
393 data: ImageData {
394 width: tlg.width,
395 height: tlg.height,
396 color_type: match tlg.color {
397 TlgColorType::Bgr24 => ImageColorType::Bgr,
398 TlgColorType::Bgra32 => ImageColorType::Bgra,
399 TlgColorType::Grayscale8 => ImageColorType::Grayscale,
400 },
401 depth: 8,
402 data: tlg.data.clone(),
403 },
404 }))
405 }
406 None => None,
407 }
408 }
409}